class Vector {
  constructor(x, y) {
    this.x = x || 0;
    this.y = y || 0;
  }

  add(v) {
    return new Vector(this.x + v.x, this.y + v.y);
  }

  addTo(v) {
    this.x += v.x;
    this.y += v.y;
  }

  sub(v) {
    return new Vector(this.x - v.x, this.y - v.y);
  }

  subFrom(v) {
    this.x -= v.x;
    this.y -= v.y;
  }

  mul(n) {
    return new Vector(this.x * n, this.y * n);
  }

  mulTo(n) {
    this.x *= n;
    this.y *= n;
    return this;
  }

  div(n) {
    if (n === 0) {
      throw new Error("div by zero");
    }
    return new Vector(this.x / n, this.y / n);
  }

  divTo(n) {
    if (n === 0) {
      throw new Error("div by zero");
    }
    this.x /= n;
    this.y /= n;
  }

  setAngle(angle) {
    const length = this.getLength();
    this.x = Math.cos(angle) * length;
    this.y = Math.sin(angle) * length;
  }

  normalize() {
    const length = this.length();
    if (length > 0) {
      this.x /= length;
      this.y /= length;
    }
    return this;
  }

  getLength() {
    // return Math.sqrt(this.x * this.x + this.y * this.y);
    return Math.hypot(this.x, this.y);
  }

  getAngle() {
    return Math.atan2(this.y, this.x);
  }

  setLength(length) {
    const angle = this.getAngle();
    this.x = Math.cos(angle) * length;
    this.y = Math.sin(angle) * length;
  }

  distance(v) {
    const x = this.x - v.x;
    const y = this.y - v.y;
    // return Math.sqrt(x * x + y * y);
    return Math.hypot(x, y);
  }

  distanceSq(v) {
    const x = this.x - v.x;
    const y = this.y - v.y;
    return x * x + y * y;
  }

  distanceTo(v) {
    return this.sub(v).getLength();
  }

  manhattanDistanceTo(v) {
    return Math.abs(v.x - this.x) + Math.abs(v.y - this.y);
  }

  distanceToSq(v) {
    return this.sub(v).getLengthSq();
  }

  reset() {
    this.x = 0;
    this.y = 0;
    return this;
  }

  neg() {
    this.x *= -1;
    this.y *= -1;
    return this;
  }

  copy() {
    return new Vector(this.x, this.y);
  }

  rotate(angle) {
    return new Vector(
      this.x * Math.cos(angle) - this.y * Math.sin(angle),
      this.x * Math.sin(angle) + this.y * Math.cos(angle)
    );
  }

  rotateTo(angle) {
    let x = this.x * Math.cos(angle) - this.y * Math.sin(angle);
    let y = this.x * Math.sin(angle) + this.y * Math.cos(angle);
    this.x = x;
    this.y = y;
    return this;
  }

  rotateAround(v, angle) {
    let x =
      (this.x - v.x) * Math.cos(angle) - (v.y - this.y) * Math.sin(angle) + v.x;
    let y =
      (this.x - v.x) * Math.sin(angle) + (v.y - this.y) * Math.cos(angle) + v.y;
    return new Vector(x, y);
  }

  rotateMeAround(v, angle) {
    let x =
      (this.x - v.x) * Math.cos(angle) - (v.y - this.y) * Math.sin(angle) + v.x;
    let y =
      (this.x - v.x) * Math.sin(angle) + (v.y - this.y) * Math.cos(angle) + v.y;
    this.x = x;
    this.y = y;
    return this;
  }

  static add(v1, v2) {
    if (v2.x != null && v2.y != null) {
      return new Vector(v1.x + v2.x, v1.y + v2.y);
    } else {
      return new Vector(v1.x + v2, v1.y + v2);
    }
  }

  static sub(v1, v2) {
    if (v2.x != null && v2.y != null) {
      return new Vector(v1.x - v2.x, v1.y - v2.y);
    } else {
      return new Vector(v1.x - v2, v1.y - v2);
    }
  }

  static mul(v1, v2) {
    if (v2.x != null && v2.y != null) {
      return new Vector(v1.x * v2.x, v1.y * v2.y);
    } else {
      return new Vector(v1.x * v2, v1.y * v2);
    }
  }

  static div(v1, v2) {
    if (v2.x != null && v2.y != null) {
      return new Vector(v1.x / v2.x, v1.y / v2.y);
    } else {
      return new Vector(v1.x / v2, v1.y / v2);
    }
  }
}
